/*
 * @(#)XfdmFuzzyCN2.java        1.0 2008/20/05
 *
 * This file is part of Xfuzzy 3.0, a design environment for fuzzy logic
 * based systems.
 *
 * (c) 2000 IMSE-CNM. The authors may be contacted by the email address:
 *                    xfuzzy-team@imse.cnm.es
 *
 * Xfuzzy is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * Xfuzzy is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 */

package xfuzzy.xfdm.model.algorithm;

import xfuzzy.lang.*;
import xfuzzy.xfdm.model.XfdmAlgorithm;
import xfuzzy.xfdm.model.XfdmConfig;
import xfuzzy.xfds.XfdsDataSet;

import java.util.ArrayList;

/**
 * Clase que desarrolla el algoritmo FuzzyCN2
 * 
 * @author Pablo Mart�n Mu�oz
 * @author Francisco Jos� Moreno Velo
 *
 */
public class XfdmFuzzyCN2 extends XfdmAlgorithm {
	
	//----------------------------------------------------------------------------//
	//                            MIEMBROS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * N�mero de antecedentes a mantener en la estrella
	 */
	private int star_size;
	
	/**
	 * Soporte absoluto minimo
	 */
	private double minimum_absolute_support;
	
	/**
	 * Soporte relativo minimo
	 */
	private double minimum_relative_support;
	
	/**
	 * Corte-alpha para discriminar instancias positivas
	 */
	private double cut;
	
	/**
	 * Lista de modificadores linguisticos seleccionados en la configuracion
	 */
	private boolean hedges[]; 
	
	//----------------------------------------------------------------------------//
	//                                CONSTRUCTOR                                 //
	//----------------------------------------------------------------------------//

	/**
	 * Constructor
	 */
	public XfdmFuzzyCN2(int s, double c, double mas, double mrs, int hedgesCode){
		this.star_size = s;
		this.cut = c;
		this.minimum_absolute_support = mas;
		this.minimum_relative_support = mrs;
		this.hedges = getHedgesFromCode(hedgesCode);
	}
	
	//----------------------------------------------------------------------------//
	//                             M�TODOS P�BLICOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Indica si el algoritmo necesita que las variables de entrada tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de entrada.
	 */
	public boolean needInputMemFuncs() {
		return true;
	}
	
	/**
	 * Indica si el algoritmo necesita que las variables de salida tengan creadas
	 * las funciones de pertenencia. En caso contrario el algoritmo debe encargarse
	 * de dar contenido a los tipos de las variables de salida.
	 */
	public boolean needOutputMemFuncs() {
		return true;
	}

	/**
	 * Indica si el algoritmo necesita que la estructura del sistema est� creada.
	 * Este requisito es propio de los algoritmo que contienen alguna etapa de
	 * optimizaci�n param�trica.
	 * @return
	 */
	public boolean needSystemStructure() {
		return false;
	}
	
	/**
	 * Indica si el algoritmo es v�lido para problemas de clasificaci�n
	 * @return
	 */
	public boolean supportsClassification() {
		return true;
	}
	
	/**
	 * Indica si el algoritmo es v�lido para problemas de regresi�n
	 * @return
	 */
	public boolean supportsRegression() {
		return false;
	}

	/**
	 * Obtiene el nombre del algoritmo
	 */
	public String toString() {
		return "FuzzyCN2";
	}

	/**
	 * Representaci�n en el fichero de configuraci�n
	 */
	public String toXML() {
		int hedgesCode = getHedgesCode();
		String eol = System.getProperty("line.separator", "\n");
		String code = "\t\t<algorithm name=\"FuzzyCN2\" >"+eol;
		code += "\t\t\t<param name=\"star_size\" value=\""+star_size+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"alpha_cut\" value=\""+cut+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"minimum_absolute_support\" value=\""+minimum_absolute_support+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"minimum_relative_support\" value=\""+minimum_relative_support+"\" ></param>"+eol;
		code += "\t\t\t<param name=\"hedges\" value=\""+hedgesCode+"\" ></param>"+eol;
		code +=	"\t\t</algorithm>";
		return code;
	}

	/**
	 * Obtiene el tipo de base de conocimiento que genera el algoritmo
	 */
	public int getKnowledgeBaseKind() {
		return KnowledgeBase.RULE_LIST;
	}
	
	/**
	 * Obtiene un duplicado del objeto
	 */
	public Object clone() {
		return new XfdmFuzzyCN2(star_size,cut,minimum_absolute_support,minimum_relative_support,getHedgesCode());
	}
	
	/**
	 * Asigna el valor al soporte absoluto minimo
	 * @param l
	 */
	public void setMinimumAbsoluteSupport(double mas) {
		this.minimum_absolute_support = mas;
	}
	
	/**
	 * Obtiene el valor del soporte absoluto minimo
	 * @return
	 */
	public double getMinimumAbsoluteSupport() {
		return this.minimum_absolute_support;
	}
	
	/**
	 * Asigna el valor al soporte relativo minimo
	 * @param l
	 */
	public void setMinimumRelativeSupport(double mrs) {
		this.minimum_relative_support = mrs;
	}
	
	/**
	 * Obtiene el valor del soporte relativo minimo
	 * @return
	 */
	public double getMinimumRelativeSupport() {
		return this.minimum_relative_support;
	}

	/**
	 * Asigna el valor del tama�o de la estrella
	 * @param s
	 */
	public void setStarSize(int s) {
		this.star_size = s;
	}
	
	/**
	 * Obtiene el valor del tama�o de la estrella
	 * @return
	 */
	public int getStarSize() {
		return this.star_size;
	}
	
	/**
	 * Asigna el valor del corte-alpha
	 * @param c
	 */
	public void setCut(double c) {
		this.cut = c;
	}
	
	/**
	 * Obtiene el valor del corte-alpha
	 * @return
	 */
	public double getCut() {
		return this.cut;
	}
	
	/**
	 * Asigna el valor al parametro i-esimo de la lista de modificadores linguisticos
	 */
	public void setHedges(int i, boolean value) {
		this.hedges[i] = value;
	}
	
	/**
	 * Obtiene el valor del parametro i-esimo de la lista de modificadores linguisticos
	 */
	public boolean getHedges(int i) {
		return this.hedges[i];
	}
	
	/**
	 * Genera el contenido de la base de conocimiento a partir de los datos
	 */
	public void extractKnowlegde(KnowledgeBase base, XfdmConfig config) {
		XfdsDataSet dataset = config.getDataSet();
		Variable[] ivar = base.getInputs();
		Variable[] ovar = base.getOutputs();
		Type[] itype = new Type[ivar.length];
		for(int i=0; i<ivar.length; i++) itype[i] = ivar[i].getType();
		XfdmFuzzyCN2DataSet fzdataset = new XfdmFuzzyCN2DataSet(dataset,itype,ovar[0].getType());
		computeFuzzyCN2(fzdataset, (RuleList) base);
	}

	//----------------------------------------------------------------------------//
	//                             M�TODOS PRIVADOS                               //
	//----------------------------------------------------------------------------//

	/**
	 * Ejecuta el bucle externo del algoritmo FuzzyCN2 y genera las reglas
	 */
	private void computeFuzzyCN2(XfdmFuzzyCN2DataSet dataset, RuleList base) {
		XfdmFuzzyCN2Complex complex;
		
		do {
			complex = findBestComplex(dataset, base);
			if(complex != null) {
				Conclusion conc = findConsequentToComplex(base,complex,dataset);
				Relation rel = createRelationFromComplex(base,complex);
				RL_Rule rule = null;
				if(base.getNumOfRules() == 0) {
					//Regla IF
					rule = new RL_Rule(rel,RL_Rule.FIRST_RULE);
				}
				else {
					//Regla ELSEIF
					rule = new RL_Rule(rel,RL_Rule.INNER_RULE);
				}
				rule.add(conc);
				base.addRule(rule);
				//System.out.println(rule.toXfl());
				
				// Deactivate
				for(int i=0; i<dataset.certainty.length; i++) {
					dataset.certainty[i] = (1-complex.compute(dataset.input[i])) * dataset.certainty[i];
				}
				
				//Truncar dataset
				for(int i=0; i<dataset.certainty.length; i++) {
					if(dataset.certainty[i] < this.cut) dataset.certainty[i] = 0.0; 
				}
			}
		} while ((complex != null) && (!dataset.isEmpty()));
		
		// RL_Rule lastrule = new RL_Rule(null,RL_Rule.LAST_RULE);
		// lastrule.add(defaultClass);
		// base.addRule(lastrule);		
	}
	
	/**
	 * Obtiene el mejor complejo (antecedente) para el conjunto de ejemplos
	 * @return
	 */
	private XfdmFuzzyCN2Complex findBestComplex(XfdmFuzzyCN2DataSet ds, RuleList base) {
		XfdmFuzzyCN2Selector[] selectors = createSelectorList(base);
		int depth = 0;
		int max_depth = base.getInputs().length;
		
		//Star es una lista de complejos
		ArrayList<XfdmFuzzyCN2Complex> star = new ArrayList<XfdmFuzzyCN2Complex>();
		star.add(new XfdmFuzzyCN2Complex(base.getOperatorset()));
		XfdmFuzzyCN2Complex best_complex = null;
		double best_quality = 0.0;
		while(star.size()>0 && depth<=max_depth) {
			ArrayList<XfdmFuzzyCN2Complex> newstar = new ArrayList<XfdmFuzzyCN2Complex>();
			double[] quality = new double[0];
			for(int i=0;i<star.size();i++) { //para cada complejo de la estrella
				for(int s=0; s<selectors.length; s++) { // para cada selector
					XfdmFuzzyCN2Complex complex = addSelector(star.get(i),selectors[s]);
					if(complex == null) continue;
					if(!isSignificant(complex,ds)) continue;
					double complex_quality = getQuality(complex,ds);
					if(complex_quality>best_quality) {
						best_quality = complex_quality;
						best_complex = complex;
					}
					newstar = appendComplex(newstar,quality,complex,complex_quality);
					quality = appendQuality(quality,complex_quality);
				}
			}
			star = newstar;
			depth++;
		}
		
		return best_complex;
	}

	/**
	 * 
	 * @return
	 */
	private XfdmFuzzyCN2Selector[] createSelectorList(RuleList base) {
		//Calcula el numero de selectores
		int length = 0;
		Variable[] ivar = base.getInputs();
		for(int a=0; a<ivar.length; a++) { 
			length += ivar[a].getType().getAllMembershipFunctions().length;
		}
		int counter = 1;
		for(int i=0;i<this.hedges.length;i++) {
			if(this.hedges[i]) {
				counter++;
			}
		}
		length *= counter;
		
		int[] hedges = createHedges(counter);
		
		//Crea la lista de selectores
		XfdmFuzzyCN2Selector[] selectors = new XfdmFuzzyCN2Selector[length];
		for(int v=0,s=0; v<ivar.length; v++) {
			LinguisticLabel[] mfs = ivar[v].getType().getAllMembershipFunctions();
			for(int m=0; m<mfs.length; m++) {
				for(int h=0; h<hedges.length; h++) {
					selectors[s] = new XfdmFuzzyCN2Selector(base.getOperatorset());
					selectors[s].setVarIndex(v);
					selectors[s].setMf(mfs[m]);
					selectors[s].setKind(hedges[h]);
					s++;
				}
			}
		}
		return selectors;
	}
	
	/**
	 * Crea al lista de los posibles modificadores linguisticos como relaciones
	 * @param count
	 */
	private int[] createHedges(int count) {
		int[] h = new int[count];
		h[0] = Relation.IS;
		for(int i=0;i<this.hedges.length;i++) {
			if(this.hedges[i]) {
				switch(i) {
					case 0:
						h[i+1] = Relation.ISNOT;
						break;
					case 1:
						h[i+1] = Relation.GR_EQ;
						break;
					case 2:
						h[i+1] = Relation.SM_EQ;
						break;
					case 3:
						h[i+1] = Relation.GREATER;
						break;
					case 4:
						h[i+1] = Relation.SMALLER;
						break;
					case 5:
						h[i+1] = Relation.APP_EQ;
						break;
					case 6:
						h[i+1] = Relation.VERY_EQ;
						break;
					case 7:
						h[i+1] = Relation.SL_EQ;
						break;
				}
			}
		}
		return h; 
	}
	
	/**
	 * Obtiene el mejor complejo (antecedente) para el conjunto de ejemplos
	 * @return
	 */
	private Conclusion findConsequentToComplex(RuleList base,XfdmFuzzyCN2Complex complex,XfdmFuzzyCN2DataSet ds) {
		LinguisticLabel[] outputMfs = ds.getClassif().getAllMembershipFunctions();
		int numClasses = outputMfs.length;
		
		// Calcula el soporte de cada clase
		double support[] = new double[numClasses];
		for(int i=0;i<ds.certainty.length;i++) {
			support[ds.output[i]] += (complex.compute(ds.input[i]) * ds.certainty[i]);
		}
		
		//Calcula la clase mayoritaria
		double max_support = support[0];
		int max_index = 0;
		for(int i=1; i<support.length; i++) { 
			if(support[i]>max_support) {
				max_support = support[i];
				max_index = i; 
			}
		}
	
		Variable var = base.getOutputs()[0];
		Conclusion conc = new Conclusion(var,outputMfs[max_index],base);
		return conc;
	}
	
	/**
	 * Obtiene la clase m�s poblada
	 * @return
	 */
	@SuppressWarnings("unused")
	private Conclusion findDefaultClass(RuleList base, XfdmFuzzyCN2DataSet ds) {
		LinguisticLabel[] outputMfs = ds.getClassif().getAllMembershipFunctions();
		int numClasses = outputMfs.length;
		
		// Calcula el soporte de cada clase
		double support[] = new double[numClasses];
		for(int i=0;i<ds.certainty.length;i++) {
			support[ds.output[i]] += ds.certainty[i];
		}
		
		//Calcula la clase mayoritaria
		double max_support = support[0];
		int max_index = 0;
		for(int i=1; i<support.length; i++) { 
			if(support[i]>max_support) {
				max_support = support[i];
				max_index = i; 
			}
		}
	
		Variable var = base.getOutputs()[0];
		Conclusion conc = new Conclusion(var,outputMfs[max_index],base);
		return conc;
	}
	
	/**
	 * Crea un objeto relation a partir de un complejo
	 */
	private Relation createRelationFromComplex(RuleList base, XfdmFuzzyCN2Complex complex) {
		Variable[] ivar = base.getInputs();
		int var0 = complex.selectores.get(0).getVarIndex();
		LinguisticLabel mf0 = complex.selectores.get(0).getMf();
		Relation rel = Relation.create(complex.selectores.get(0).getKind(),null,null,ivar[var0],mf0,base);
		for(int i=1; i<complex.selectores.size(); i++) {
			int var = complex.selectores.get(i).getVarIndex();		//Variable del selector i-esimo
			LinguisticLabel mf = complex.selectores.get(i).getMf();	//Etiqueta del selector i-esimo
			Relation right = Relation.create(complex.selectores.get(i).getKind(),null,null,ivar[var],mf,base);
			rel = Relation.create(Relation.AND,rel,right,null,null,base);
		}
		return rel;
	}
	
	/**
	 * A�ade un selector a un complejo. Si se repite el selector devuelve null.
	 * @param complex
	 * @param selector
	 * @return
	 */
	private XfdmFuzzyCN2Complex addSelector(XfdmFuzzyCN2Complex complex, XfdmFuzzyCN2Selector selector) {
		int selvar = selector.getVarIndex();
		int selkind = selector.getKind();
		LinguisticLabel selmf = selector.getMf();

		// Si el selector ya est� en el complejo, no se a�ade
		for(int i=0;i<complex.selectores.size();i++) {
			if((complex.selectores.get(i).getVarIndex() == selvar) 
					&& (complex.selectores.get(i).getKind() == selkind)
					&& (complex.selectores.get(i).getMf() == selmf)) {
				return null;
			}
		}
		
		// Si la variable del selector ya est� dos veces en el complejo, no se a�ade
		int selvarcount = 0;
		for(int i=0;i<complex.selectores.size();i++) {
			if(complex.selectores.get(i).getVarIndex() == selvar) selvarcount++;
		}
		if(selvarcount >= 2) return null;
		
		XfdmFuzzyCN2Complex newcomplex = new XfdmFuzzyCN2Complex(complex.getOpSet());
		for(int i=0;i<complex.selectores.size();i++) {
			XfdmFuzzyCN2Selector s = new XfdmFuzzyCN2Selector(complex.getOpSet());
			s.setVarIndex(complex.selectores.get(i).getVarIndex());
			s.setKind(complex.selectores.get(i).getKind());
			s.setMf(complex.selectores.get(i).getMf());
			newcomplex.selectores.add(s);
		}
		newcomplex.selectores.add(selector);
		return newcomplex;
	}
	
	/**
	 * A�ade un complejo a la estrella. Si se llega al tama�o m�ximo se elimina el peor de los complejos.
	 * @param star
	 * @param quality
	 * @param complex
	 * @param complex_quality
	 * @return
	 */
	private ArrayList<XfdmFuzzyCN2Complex> appendComplex(ArrayList<XfdmFuzzyCN2Complex> star, double[] quality, XfdmFuzzyCN2Complex  complex, double complex_quality){
		if(star.size() < star_size) {
			ArrayList<XfdmFuzzyCN2Complex> append = new ArrayList<XfdmFuzzyCN2Complex>();
			for(int i=0; i<star.size(); i++) append.add(star.get(i));
			append.add(complex);
			return append;
		}
		else {
			double worst = quality[0];
			int worst_index = 0;
			for(int i=0;i<quality.length;i++) if(quality[i]<worst) { worst=quality[i]; worst_index = i; }
			if(worst<complex_quality) { star.set(worst_index,complex); }
			return star;
		}	
	}

	/**
	 * A�ade una evaluaci�n de una complejo a la lista de evaluaciones. Si se llega al tama�o se
	 * elimina el valor m�s bajo 
	 * @param quality
	 * @param complex_quality
	 * @return
	 */
	private double[] appendQuality(double[] quality, double complex_quality){
		if(quality.length < star_size) {
			double[] append = new double[ quality.length+1 ];
			for(int i=0; i<quality.length; i++) append[i]=quality[i];
			append[quality.length] = complex_quality;
			return append;
		} else {
			double worst = quality[0];
			int worst_index = 0;
			for(int i=1; i<quality.length; i++) if(quality[i]<worst) { worst=quality[i]; worst_index = i; }
			if(worst<complex_quality) { quality[worst_index] = complex_quality; }
			return quality;
		}
	}

	/**
	 * Estudia si el complejo es estad�sticamente significativo. Para ello estudia
	 * el n�mero de instancias positivas que cubre y verifica que sea mayor o igual
	 * que el umbral de significancia.
	 * @param complex Complejo a estudiar
	 * @return
	 */
	private boolean isSignificant(XfdmFuzzyCN2Complex complex, XfdmFuzzyCN2DataSet dataset) {
		int instances = dataset.certainty.length;
		if(instances == 0) return false;
		double num = 0.0;
		double denom = 0.0;
		for(int i=0; i<instances; i++) {
			num = num + (dataset.certainty[i] * complex.compute(dataset.input[i]));
			denom = denom + (dataset.certainty[i]);
		}
		if (denom == 0) return false;
		else 
			if ((1.0*(num/denom) >= this.minimum_relative_support) && (num >= this.minimum_absolute_support)) return true;
			else return false;
	}
		
	/**
	 * Calcula la medida de precision difusa del complejo (fuzzy accuracy) mediante el error laplaciano
	 * @param complex
	 * @param ds = dataset
	 * @return
	 */
	private double getQuality(XfdmFuzzyCN2Complex complex, XfdmFuzzyCN2DataSet ds) {
		int numClasses = ds.getClassif().getAllMembershipFunctions().length;
		
		// Calcula el soporte de cada clase
		double support[] = new double[numClasses];
		for(int i=0;i<ds.certainty.length;i++) {
			support[ds.output[i]] += (complex.compute(ds.input[i]) * ds.certainty[i]);
		}
		
		//Calcula la clase mayoritaria
		double max_support = support[0];
		for(int i=1; i<support.length; i++) if(support[i]>max_support) max_support = support[i];

		// Calcula el soporte total
		double acc_support = 0.0;
		for(int i=0; i<support.length; i++) acc_support += support[i];
		
		return (max_support +1)/(acc_support+numClasses);
	}
	
	/**
	 * Obtiene el c�digo entero asociado a la selecci�n de modificadores ling��sticos
	 * @return
	 */
	private int getHedgesCode() {
		int hedgesCode = 0;
		if(hedges[0]) hedgesCode += 1;
		if(hedges[1]) hedgesCode += 2;
		if(hedges[2]) hedgesCode += 4;
		if(hedges[3]) hedgesCode += 8;
		if(hedges[4]) hedgesCode += 16;
		if(hedges[5]) hedgesCode += 32;
		if(hedges[6]) hedgesCode += 64;
		if(hedges[7]) hedgesCode += 128;
		return hedgesCode;
	}
	
	/**
	 * Obtiene la selecci�n de modificadores ling��sticos a partir de su c�digo asociado
	 * @param hedgesCode
	 * @return
	 */
	private boolean[] getHedgesFromCode(int hedgesCode) {
		int code = hedgesCode%256;
		boolean[] hdg = new boolean[8];
		if(code >= 128) { hdg[7] = true; code -= 128; }
		if(code >= 64) { hdg[6] = true; code -= 64; }
		if(code >= 32) { hdg[5] = true; code -= 32; }
		if(code >= 16) { hdg[4] = true; code -= 16; }
		if(code >= 8) { hdg[3] = true; code -= 8; }
		if(code >= 4) { hdg[2] = true; code -= 4; }
		if(code >= 2) { hdg[1] = true; code -= 2; }
		if(code >= 1) { hdg[0] = true; code -= 1; }
		return hdg;
	}
}
